﻿using System;
using System.IO;

using Nextensions.Security.Cryptography;

namespace gozer
{
    public static class Serializer
    {
        private static void WriteItemToFile(string PassPhrase, string Key, string Value, string FilePath, int DestinationIndex)
        {
            if (DestinationIndex == -1)
            {
                using (FileStream fs = new FileStream(FilePath, FileMode.Append, FileAccess.Write))
                {
                    using (BinaryWriter writer = new BinaryWriter(fs))
                    {
                        byte[] headerKeyAndVal = GetBytesForSerializing(Key, Value, PassPhrase);
                        writer.Write(headerKeyAndVal);
                        writer.Close();
                    }
                    fs.Close();
                }
            }
            else
            {
                byte[] file = File.ReadAllBytes(FilePath);
                byte[] headerBytes = new byte[Header.Length];
                Array.Copy(file, DestinationIndex, headerBytes, 0, headerBytes.Length); // Here, DestinationIndex is used as source index, because it is the item we are replacing whose length we need to know in order to replace it.
                Header existingItemHeader = Header.Parse(headerBytes);
                byte[] encryptedValue = CryptTool.Encrypt(Value, PassPhrase); // Encrypt the specified value so we can write it's bytes to the file
                int diff = encryptedValue.Length - existingItemHeader.ValueLength; // We need to know the length of the new byte array that we are going to (over)write to the existing file. The only possible difference is the new specified value, because the key is the same.
                byte[] newFile = new byte[file.Length + diff];

                int readPos = 0;
                int writePos = 0;
                int len = DestinationIndex;

                Array.Copy(file, readPos, newFile, writePos, len);// Copy and write the part of the existing file up-to the part we are replacing...

                writePos += len;
                readPos += len + existingItemHeader.TotalLength;

                byte[] serialized = GetBytesForSerializing(Key, Value, PassPhrase);
                len = serialized.Length;

                Array.Copy(serialized, 0, newFile, writePos, len); // Write our newly serialized header, key & value.

                if (readPos < file.Length) // Check if we reached the end of the original file (if our new file is longer than our old file (like if the existing file only has one value and we just made it longer))
                {
                    writePos += len;
                    Array.Copy(file, readPos, newFile, writePos, file.Length - readPos); // Copy and write the last part of the existing file (which begins at the end of the existing value).
                }

                

                File.WriteAllBytes(FilePath, newFile); // Replace the entirety of the existing file with the one we cosntructed.
            }
        }

        public static void WriteItemToFile(string PassPhrase, string Key, string Value, string FilePath, bool OverrideExistingItem)
        {
            int matchIndex;
            string existingValue = GetItemValue(PassPhrase, Key, FilePath, out matchIndex);

            WriteItemToFile(PassPhrase, Key, Value, FilePath, matchIndex);
            return;

            byte[] file = File.ReadAllBytes(FilePath);

            int pos = 0;

            while (pos < file.Length - 1)
            {
                byte[] headerBytes = new byte[2] { file[pos], file[pos + 1] };
                Header h;
                
                if (!Header.TryParse(headerBytes, out h))
                {
                    throw new System.NotImplementedException("Invalid header.");
                }

                pos += Header.Length;

                byte[] currentKeyBytes = new byte[h.KeyLength];
                Array.Copy(file, pos, currentKeyBytes, 0, currentKeyBytes.Length); // Copy the bytes for the current key into the keyBytes array.

                string currentKey = CryptTool.Decrypt(currentKeyBytes, PassPhrase); // Decrypt the key bytes into a string

                if (currentKey == Key) // We found the key we're looking for
                {
                    if (!OverrideExistingItem)
                    {
                        throw new Exception("Found an existing item with the specified key, but 'OverrideExistingItem' was specified as false.");
                    }

                    byte[] encryptedValue = CryptTool.Encrypt(Value, PassPhrase); // Encrypt the specified value so we can write it's bytes to the file
                    int diff = encryptedValue.Length - h.ValueLength; // We need to know the length of the new byte array that we are going to (over)write to the existing file. The only possible difference is the new specified value, because the key is the same.
                    byte[] newFile = new byte[file.Length + diff];

                    

                    return;
                }
                else
                {
                    pos += h.KeyLength + h.ValueLength;
                }
            }
        }

        public static string GetItemValue(string PassPhrase, string Key, string FilePath)
        {
            int index;
            string val = GetItemValue(PassPhrase, Key, FilePath, out index);
            return val;
        }

        public static string GetItemValue(string PassPhrase, string Key, string FilePath, out int Index)
        {
            Index = -1;

            if (!File.Exists(FilePath))
                return null;

            byte[] file = File.ReadAllBytes(FilePath);

            int pos = 0;

            while (pos < file.Length - 1)
            {
                byte[] headerBytes = new byte[2] { file[pos], file[pos + 1] };
                Header h = Header.Parse(headerBytes);

                int headerPos = pos;
                pos += Header.Length;

                byte[] currentKeyBytes = new byte[h.KeyLength];
                Array.Copy(file, pos, currentKeyBytes, 0, currentKeyBytes.Length);

                pos += currentKeyBytes.Length;

                string currentKey = CryptTool.Decrypt(currentKeyBytes, PassPhrase);

                if (Key == currentKey)
                {
                    byte[] valBytes = new byte[h.ValueLength];
                    Array.Copy(file, pos, valBytes, 0, valBytes.Length);
                    Index = headerPos;
                    string decryptedVal = CryptTool.Decrypt(valBytes, PassPhrase);
                    return decryptedVal;
                }

                pos += h.ValueLength;
            }

            return null;
        }

        private static byte[] GetBytesForSerializing(string Key, string Value, string PassPhrase)
        {
            byte[] result;
            byte[] keyBytes;
            byte[] valBytes;

            keyBytes = CryptTool.Encrypt(Key, PassPhrase);
            valBytes = CryptTool.Encrypt(Value, PassPhrase);

            result = new byte[Header.Length + keyBytes.Length + valBytes.Length];

            result[0] = (byte)keyBytes.Length;
            result[1] = (byte)valBytes.Length;

            int pos = Header.Length;

            Array.Copy(keyBytes, 0, result, pos, keyBytes.Length);
            pos += keyBytes.Length;
            Array.Copy(valBytes, 0, result, pos, valBytes.Length);

            return result;
        }
    }
}
